package main

import (
	"go/ast"
	"go/parser"
	"fmt"
	"flag"
	. "os"
	//"reflect"
)

// Visitor for ast.Walk

type printVisitor struct {
	weight float
        priorWeight float
}

func (f *printVisitor) Visit(node interface{}) ast.Visitor {

	if node == nil {
		return f
	}

	//fmt.Println(reflect.NewValue(node))

	//fmt.Printf("weight: %f\n", f.weight)

	switch n := node.(type) {

	// Declarations
	case *ast.ImportSpec:
		fmt.Println("Import declaration")

	case *ast.ValueSpec:
		fmt.Println("Constant or variable declaration")

	case *ast.TypeSpec:
		fmt.Println("Type declaration")

	case *ast.BadDecl:
		fmt.Println("Bad declaration")

	case *ast.FuncDecl:
		fmt.Printf("Function declaration: %s\n", n.Name)
		// Include the receiver in the parameters count
		if (n.Recv != nil) {
			fmt.Printf("Number of function parameters: 1\n")
		}
		fmt.Printf("Number of statements in function body: %d\n", len(n.Body.List))

	/*
	case []ast.Decl:
		fmt.Printf("Total number of declarations %d\n", len(n))
	*/

	// Statements (weighted)
	case *ast.BadStmt:
		fmt.Printf("Bad statement: %f\n", f.weight)

	case *ast.DeclStmt:
		fmt.Printf("Declaration statement: %f\n", f.weight)

	case *ast.EmptyStmt:
		fmt.Printf("Empty statement: %f\n", f.weight)

	case *ast.LabeledStmt:
		fmt.Printf("Labeled statement: %f\n", f.weight)
	/*
	case *ast.ExprStmt:
		fmt.Println("Expression statement")
	*/
	case *ast.IncDecStmt:
		fmt.Printf("Increment or decrement statement: %f\n", f.weight)

	case *ast.AssignStmt:
		fmt.Printf("Assignment statement: %f\n", f.weight)

	case *ast.GoStmt:
		fmt.Printf("Go statement: %f\n", f.weight)

	case *ast.DeferStmt:
		fmt.Printf("Defer statement: %f\n", f.weight)

	case *ast.ReturnStmt:
		fmt.Printf("Return statement: %f\n", f.weight)

	case *ast.BranchStmt:
		fmt.Printf("Branch statement: %f\n", f.weight)

	case *ast.BlockStmt:
		fmt.Println("Block statement")

	case *ast.IfStmt:
		fmt.Printf("If statement: %f\n", f.weight)
		var t printVisitor
		t.weight = f.weight/2
		t.priorWeight = f.weight
		return &t

	case *ast.CaseClause:
		fmt.Printf("Case clause of expression switch statement: %f\n", f.priorWeight)

	case *ast.SwitchStmt:
		fmt.Printf("Switch statement: %f\n", f.weight)
		numCases := len(n.Body.List)
                //fmt.Printf("Number of cases: %d\n", numCases)
		var t printVisitor
		t.weight = f.weight/float(numCases)
		t.priorWeight = f.weight
		return &t

	case *ast.TypeCaseClause:
		fmt.Printf("Case clause of type switch statement: %f\n", f.priorWeight)

	case *ast.TypeSwitchStmt:
		fmt.Printf("Type switch statement: %f\n", f.weight)
		numCases := len(n.Body.List)
                //fmt.Printf("Number of cases: %d\n", numCases)
		var t printVisitor
		t.weight = f.weight/float(numCases)
		t.priorWeight = f.weight
		return &t

	case *ast.CommClause:
		fmt.Printf("Case clause of select statement: %f\n", f.priorWeight)

	case *ast.SelectStmt:
		fmt.Printf("Select statement: %f\n", f.weight)
		numCases := len(n.Body.List)
                //fmt.Printf("Number of cases: %d\n", numCases)
		var t printVisitor
		t.weight = f.weight/float(numCases)
		t.priorWeight = f.weight
		return &t

	case *ast.ForStmt:
		fmt.Printf("For statement: %f\n", f.weight)
		var t printVisitor
		t.weight = f.weight * 10
                t.priorWeight = f.weight
		return &t

	case *ast.RangeStmt:
		fmt.Printf("Range statement: %f\n", f.weight)

	//Types
	case *ast.FuncType:
		fmt.Println("Function type")
		fmt.Printf("Number of function parameters: %d\n", n.Params.NumFields())
		fmt.Printf("Number of function results: %d\n", n.Results.NumFields())

	case *ast.ArrayType:
		fmt.Println("Array type")

	case *ast.StructType:
		fmt.Println("Struct type")

	case *ast.InterfaceType:
		fmt.Println("Interface type")

	case *ast.MapType:
		fmt.Println("Map type")

	case *ast.ChanType:
		fmt.Println("Channel type")

	// Expressions	
	case *ast.FuncLit:
		fmt.Println("Function literal")

	case *ast.CompositeLit:
		fmt.Println("Composite literal")

	case *ast.BadExpr:
		fmt.Println("Bad expression")

	case *ast.Ident:
		fmt.Println("Identifier")

	case *ast.Ellipsis:
		fmt.Println("Ellipsis")

	case *ast.BasicLit:
		fmt.Println("Literal of basic type")

	case *ast.ParenExpr:
		fmt.Println("Parenthesized expression")

	case *ast.SelectorExpr:
		fmt.Println("Selector expression")

	case *ast.IndexExpr:
		fmt.Println("Index expression")

	case *ast.SliceExpr:
		fmt.Println("Slice expression")

	case *ast.TypeAssertExpr:
		fmt.Println("Type assertion expression")

	case *ast.CallExpr:
		fmt.Println("Call expression")

	case *ast.StarExpr:
		fmt.Println("Star expression")
	/*
	case *ast.UnaryExpr:
		fmt.Println("Unary expression")	
	*/
	case *ast.BinaryExpr:
		fmt.Println("Binary expression")

	case *ast.KeyValueExpr:
		fmt.Println("Key-value expression")

	// Comments 
	case *ast.Comment:
		fmt.Println("Comment")

	case *ast.CommentGroup:
		fmt.Println("Comment group")

	default:
		//don't do anything

	}

	return f
}


func size(name string) int64 {
	file, _ := Open(name, O_RDONLY, 0)
	defer file.Close()
	var buf [100]byte
	len := 0
	for {
		n, e := file.Read(buf[0:])
		len += n
		if e == EOF {
			break
		}
	}
	return int64(len)
}


func main() {
	flag.Parse()
	filename := flag.Arg(0)
	file, err := parser.ParseFile(filename, nil, 0)
	if err != nil {
		fmt.Println(filename + "\t" + err.String())
		return
	}
	fmt.Printf("File size in kB: %d\n", size(filename)/1000)
	// Instance
	var f printVisitor
	f.weight = 1
        f.priorWeight = 1
	ast.Walk(&f, file)
}
